#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_err.h"
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_touch.h"
#include "lcd_bk_aw9523b.h"

static const char* TAG = "AW9523B";

/* AW9523B registers */
// REG defination
#define INPUT_PORT0  0x00    // R
#define INPUT_PORT1  0x01    // R
#define OUTPUT_PORT0 0x02    // RW
#define OUTPUT_PORT1 0x03    // RW
#define CONFIG_PORT0 0x04    // RW
#define CONFIG_PORT1 0x05    // RW
#define INT_PORT0    0x06    // RW
#define INT_PORT1    0x07    // RW
#define REG_ID       0x10    // R
#define REG_CTL      0x11    // WR, Global Ctrl
#define MODE_PORT0   0x12    // WR
#define MODE_PORT1   0x13    // WR
#define DIM0         0x20    // W, P1_0 LED current control
#define DIM1         0x21    // W, P1_1 LED current control
#define DIM2         0x22    // W, P1_2 LED current control
#define DIM3         0x23    // W, P1_3 LED current control
#define DIM4         0x24    // W, P0_0 LED current control
#define DIM5         0x25    // W, P0_1 LED current control
#define DIM6         0x26    // W, P0_2 LED current control
#define DIM7         0x27    // W, P0_3 LED current control
#define DIM8         0x28    // W, P0_4 LED current control
#define DIM9         0x29    // W, P0_5 LED current control
#define DIM10        0x2A    // W, P0_6 LED current control
#define DIM11        0x2B    // W, P0_7 LED current control
#define DIM12        0x2C    // W, P1_4 LED current control
#define DIM13        0x2D    // W, P1_5 LED current control
#define DIM14        0x2E    // W, P1_6 LED current control
#define DIM15        0x2F    // W, P1_7 LED current control
#define SW_RSTN      0x7F    // W, Soft reset


/*******************************************************************************
 * Function definitions
 *******************************************************************************/
static esp_err_t esp_lcd_touch_aw9523b_del(esp_lcd_touch_handle_t tp);

/* I2C read/write */
static esp_err_t touch_aw9523b_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t* data, uint8_t len);
static esp_err_t touch_aw9523b_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data);

uint16_t aw9523b_read(esp_lcd_touch_handle_t tp, uint8_t _cmd);

/* AW9523B reset */
static esp_err_t touch_aw9523b_reset(esp_lcd_touch_handle_t tp);
/* Read status and config register */
static esp_err_t touch_aw9523b_read_cfg(esp_lcd_touch_handle_t tp);

/*******************************************************************************
 * Public API functions
 *******************************************************************************/

esp_err_t esp_lcd_touch_new_i2c_aw9523b(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t* config, esp_lcd_touch_handle_t* out_touch)
{
    esp_err_t ret = ESP_OK;

    assert(io != NULL);
    assert(config != NULL);
    assert(out_touch != NULL);

    esp_log_level_set(TAG, ESP_LOG_DEBUG);

    ESP_LOGD(TAG, "esp_lcd_touch_new_i2c_aw9523b init");

    /* Prepare main structure */
    esp_lcd_touch_handle_t esp_lcd_touch_aw9523b = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT);
    ESP_GOTO_ON_FALSE(esp_lcd_touch_aw9523b, ESP_ERR_NO_MEM, err, TAG, "no mem for AW9523B controller");

    /* Communication interface */
    esp_lcd_touch_aw9523b->io = io;

    /* Only supported callbacks are set */
    esp_lcd_touch_aw9523b->read_data = NULL;
    esp_lcd_touch_aw9523b->get_xy    = NULL;
    esp_lcd_touch_aw9523b->del       = esp_lcd_touch_aw9523b_del;

    /* Mutex */
    esp_lcd_touch_aw9523b->data.lock.owner = portMUX_FREE_VAL;

    /* Save config */
    memcpy(&esp_lcd_touch_aw9523b->config, config, sizeof(esp_lcd_touch_config_t));

    /* Prepare pin for touch controller reset */
    if(esp_lcd_touch_aw9523b->config.rst_gpio_num != GPIO_NUM_NC)
    {
        const gpio_config_t rst_gpio_config = {
            .mode         = GPIO_MODE_OUTPUT,
            .pin_bit_mask = BIT64(esp_lcd_touch_aw9523b->config.rst_gpio_num)};
        ret = gpio_config(&rst_gpio_config);
        ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
    }

    // Open lights
    /* Reset controller */
    ret = touch_aw9523b_reset(esp_lcd_touch_aw9523b);
    ESP_GOTO_ON_ERROR(ret, err, TAG, "AW9523B reset failed");


    esp_lcd_panel_io_tx_param(io, SW_RSTN, (uint8_t[]){0x00}, 1);
    esp_lcd_panel_io_tx_param(io, CONFIG_PORT0, (uint8_t[]){0x00}, 1);
    esp_lcd_panel_io_tx_param(io, CONFIG_PORT1, (uint8_t[]){0x00}, 1);
    esp_lcd_panel_io_tx_param(io, MODE_PORT0, (uint8_t[]){0x00}, 1);
    esp_lcd_panel_io_tx_param(io, MODE_PORT1, (uint8_t[]){0x00}, 1);
    esp_lcd_panel_io_tx_param(io, OUTPUT_PORT0, (uint8_t[]){0x00}, 1);
    esp_lcd_panel_io_tx_param(io, OUTPUT_PORT0, (uint8_t[]){0x00}, 1);
    esp_lcd_panel_io_tx_param(io, DIM0, (uint8_t[]){0xFF}, 1);
    esp_lcd_panel_io_tx_param(io, DIM1, (uint8_t[]){0xFF}, 1);
    // esp_lcd_panel_io_tx_param(io, DIM2, (uint8_t[]){0xFF}, 1);
    // esp_lcd_panel_io_tx_param(io, DIM3, (uint8_t[]){0xFF}, 1);

err:
    if(ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Error (0x%x)! Touch controller AW9523B initialization failed!", ret);
        if(esp_lcd_touch_aw9523b)
        {
            esp_lcd_touch_aw9523b_del(esp_lcd_touch_aw9523b);
        }
    }

    *out_touch = esp_lcd_touch_aw9523b;

    ESP_LOGD(TAG, "esp_lcd_touch_new_i2c_aw9523b success");

    return ret;
}


static esp_err_t esp_lcd_touch_aw9523b_del(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);

    /* Reset GPIO pin settings */
    if(tp->config.rst_gpio_num != GPIO_NUM_NC)
    {
        gpio_reset_pin(tp->config.rst_gpio_num);
    }

    free(tp);

    return ESP_OK;
}

/*******************************************************************************
 * Private API function
 *******************************************************************************/

/* Reset controller */
static esp_err_t touch_aw9523b_reset(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);

    if(tp->config.rst_gpio_num != GPIO_NUM_NC)
    {
        ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!");
        vTaskDelay(pdMS_TO_TICKS(10));
        ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!");
        vTaskDelay(pdMS_TO_TICKS(10));
    }

    return ESP_OK;
}


uint16_t aw9523b_read(esp_lcd_touch_handle_t tp, uint8_t _cmd)
{
    esp_err_t ret       = ESP_OK;
    uint8_t   tp_adc[2] = {0};

    ret = touch_aw9523b_i2c_read(tp, _cmd, tp_adc, 2);
    ESP_GOTO_ON_ERROR(ret, err, TAG, "touch_aw9523b_i2c_read error");

err:
    return (tp_adc[0] << 8 | tp_adc[1]) >> 4;
}


static esp_err_t touch_aw9523b_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t* data, uint8_t len)
{
    esp_err_t ret = ESP_OK;

    assert(tp != NULL);
    assert(data != NULL);

    /* Read data */
    // esp_lcd_panel_io_tx_param(tp->io, reg, NULL, 0);
    ret = esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
    ESP_LOGD(TAG, "touch_aw9523b_i2c_read:%X", ret);
    return ret;
}

static esp_err_t touch_aw9523b_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data)
{
    assert(tp != NULL);

    // *INDENT-OFF*
    /* Write data */
    return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1);
    // *INDENT-ON*
}
